$j.jrule = $j.jrule || {};
$j.extend($j.jrule, {
	guid : 1,
	uidPref : 'je',
	randId : function(prefix) {
		return (prefix || $j.jrule.uidPref) + ($j.jrule.guid++);
	},
	bindEv : function(el, opt) {
		var $t = this;
		if ($j.isFunction(opt.dataInit)) {
			opt.dataInit.call($t, el, opt);
		}
		if (opt.dataEvents) {
			$j.each(opt.dataEvents, function() {
				if (this.data !== undefined) {
					$j(el).bind(this.type, this.data, this.fn);
				} else {
					$j(el).bind(this.type, this.fn);
				}
			});
		}
	},
	// Form Functions
	createEl : function(eltype, options, vl, autowidth, ajaxso) {
		var elem = "", $t = this;
		function setAttributes(elm, atr) {
			$j.each(atr, function(key, value) {
				$j(elm).attr(key, value);
			});
			if (!atr.hasOwnProperty('id')) {
				$j(elm).attr('id', $j.jrule.randId());
			}
		}
		switch (eltype) {
		case "textarea":
		case "checkbox": // what code for simple checkbox
		case "select":
		case "image":
		case "file":
		case "text":
		case "password":
		case "button":
			var role;
			if (eltype === "button") {
				role = "button";
			} else {
				role = "textbox";
			}
			elem = document.createElement("input");
			elem.type = eltype;
			elem.value = vl;
			setAttributes(elem, options);
			if (eltype !== "button") {
				if (autowidth) {
					if (!options.size) {
						$j(elem).css({
							width : "98%"
						});
					}
				} else if (!options.size) {
					options.size = 20;
				}
			}
			$j(elem).attr("role", role);
			break;
		case "custom":
			elem = document.createElement("span");
			var celm = options.inputBuilder.call($t, vl, options);
			celm = $j(celm).addClass("customelement").attr({
				id : options.id,
				name : options.name
			});
			$j(elem).empty().append(celm);
			break;
		}
		return elem;
	},
});

$j.fn.jRuleInit = function(arg) {

	var p = $j.extend(true, {
		filter : null,
		columns : [],
		commonOperators : [ 'eq', 'ne', 'in', 'ni' ],
		i18n : {
			'eq' : '等于',
			'ne' : '不等',
			'lt' : '小于',
			'gt' : '大于',
			'in' : '属于',
			'ni' : '不属于',
		},
		groupOps : [ {
			op : "AND",
			text : "且"
		}, {
			op : "OR",
			text : "或"
		} ],
		groupButton : true,
		ruleButtons : true,
		onChange : null,
		afterRedraw : null,
	}, arg || {});
	return this
		.each(function() {
			var subj = this;
			if (this.filter) {
				return;
			}
			this.p = p;
			// setup filter in case if they is not defined
			if (this.p.filter === null || this.p.filter === undefined) {
				this.p.filter = {
					groupOp : this.p.groupOps[0].op,
					groupOpText : this.p.groupOps[0].text,
					rules : [],
					groups : []
				};
			}
			var i, len = this.p.columns.length, cl, isIE = /msie/i.test(navigator.userAgent) && !window.opera;

			// translating the options
			this.p.initFilter = $j.extend(true, {}, this.p.filter);

			// set default values for the columns if they are not set
			if (!len) {
				return;
			}
			for (i = 0; i < len; i++) {
				cl = this.p.columns[i];
				if (cl.stype) {
					// grid compatibility
					cl.inputtype = cl.stype;
				} else if (!cl.inputtype) {
					cl.inputtype = 'text';
				}
				if (cl.sorttype) {
					// grid compatibility
					cl.searchtype = cl.sorttype;
				} else if (!cl.searchtype) {
					cl.searchtype = 'string';
				}
				if (cl.hidden === undefined) {
					// jrule compatibility
					cl.hidden = false;
				}
				if (!cl.label) {
					cl.label = cl.name;
				}
				if (cl.index) {
					cl.name = cl.index;
				}
				if (!cl.hasOwnProperty('searchrules')) {
					cl.searchrules = {};
				}

			}
			var getGrid = function() {
				return subj;
			};
			/*
			 * moving to common randId = function() { return
			 * Math.floor(Math.random()*10000).toString(); };
			 */
			this.onchange = function() {
				return $j.isFunction(this.p.onChange) ? this.p.onChange.call(this, this.p) : false;
			};
			/*
			 * Redraw the filter every time when new field is added/deleted and field
			 * is changed
			 */
			this.reDraw = function() {
				$j.jrule.guid = 1;
				$j("table.group:first", this).remove();
				var t = this.createTableForGroup(p.filter, null);
				$j(this).append(t);
				if ($j.isFunction(this.p.afterRedraw)) {
					this.p.afterRedraw.call(this, this.p);
				}
			};
			/*
			 * Creates a grouping data for the filter @param group - object @param
			 * parentgroup - object
			 */
			this.createTableForGroup = function(group, parentgroup) {
				var that = this, i;
				// this table will hold all the group (tables) and rules (rows)
				var table = $j("<table class='group ui-widget ui-widget-content' style='border:0px none;'><tbody></tbody></table>"), align = "left";

				var tr = $j("<tr></tr>");
				table.append(tr);
				// this header will hold the group operator type and group action
				// buttons for
				// creating subgroup "+ {}", creating rule "+" or deleting the group
				// "-"
				var th = $j("<th colspan='5' align='" + align + "'></th>");
				tr.append(th);

				// dropdown for: choosing group operator type
				if (this.p.ruleButtons === true) {
					var groupOpSelect = $j("<select class='opsel'></select>");
					th.append(groupOpSelect);
					// populate dropdown with all posible group operators: or, and
					var str = "", selected;
					for (i = 0; i < p.groupOps.length; i++) {
						selected = group.groupOp === that.p.groupOps[i].op ? " selected='selected'" : "";
						str += "<option value='" + that.p.groupOps[i].op + "'" + selected + ">" + that.p.groupOps[i].text
							+ "</option>";
					}

					groupOpSelect.append(str).bind('change', function() {
						group.groupOp = $j(groupOpSelect).val();
						group.groupOpText = $j(groupOpSelect).find(":selected").text();
						that.onchange(); // signals that the filter has changed
					});
				}

				// button for adding a new subgroup
				if (this.p.groupButton) {
					var inputAddSubgroup = "<span></span>";
					inputAddSubgroup = $j("<input type='button' value='+ {}' title='Add subgroup' class='add-group'/>");
					th.append(inputAddSubgroup);
					inputAddSubgroup.bind('click', function() {
						if (group.groups === undefined) {
							group.groups = [];
						}

						group.groups.push({
							groupOp : p.groupOps[0].op,
							groupOpText : p.groupOps[0].text,
							rules : [],
							groups : []
						}); // adding a new group

						that.reDraw(); // the html has changed, force reDraw

						that.onchange(); // signals that the filter has changed
						return false;
					});
				}

				// button for adding a new rule
				if (this.p.ruleButtons === true) {
					var inputAddRule = $j("<input type='button' value='+' title='Add rule' class='add-rule ui-add'/>"), cm;
					th.append(inputAddRule);
					inputAddRule.bind('click', function() {
						// if(!group) { group = {};}
						if (group.rules === undefined) {
							group.rules = [];
						}
						for (i = 0; i < that.p.columns.length; i++) {
							// but show only serchable and serchhidden = true fields
							var searchable = true, hidden = false, ignoreHiding = false;
							if ((ignoreHiding && searchable) || (searchable && !hidden)) {
								cm = that.p.columns[i];
								break;
							}
						}

						var opr;
						if (cm.columnOperators) {
							opr = cm.columnOperators;
						} else {
							opr = that.p.commonOperators;
						}

						group.rules.push({
							field : cm.name,
							fieldText : cm.label,
							op : opr[0],
							opText : that.p.i18n[opr[0]],
							data : ""
						}); // adding a new rule

						that.reDraw(); // the html has changed, force reDraw
						// for the moment no change have been made to the rule, so
						// this will not trigger onchange event
						return false;
					});
				}

				// button for delete the group
				if (parentgroup !== null) { // ignore the first group
					var inputDeleteGroup = $j("<input type='button' value='-' title='Delete group' class='delete-group'/>");
					th.append(inputDeleteGroup);
					inputDeleteGroup.bind('click', function() {
						// remove group from parent
						for (i = 0; i < parentgroup.groups.length; i++) {
							if (parentgroup.groups[i] === group) {
								parentgroup.groups.splice(i, 1);
								break;
							}
						}

						that.reDraw(); // the html has changed, force reDraw

						that.onchange(); // signals that the filter has changed
						return false;
					});
				}

				// append subgroup rows
				if (group.groups !== undefined) {
					for (i = 0; i < group.groups.length; i++) {
						var trHolderForSubgroup = $j("<tr></tr>");
						table.append(trHolderForSubgroup);

						var tdFirstHolderForSubgroup = $j("<td class='first'></td>");
						trHolderForSubgroup.append(tdFirstHolderForSubgroup);

						var tdMainHolderForSubgroup = $j("<td colspan='4'></td>");
						tdMainHolderForSubgroup.append(this.createTableForGroup(group.groups[i], group));
						trHolderForSubgroup.append(tdMainHolderForSubgroup);
					}
				}
				if (group.groupOp === undefined) {
					group.groupOp = that.p.groupOps[0].op;
				}

				// append rules rows
				if (group.rules !== undefined) {
					for (i = 0; i < group.rules.length; i++) {
						table.append(this.createTableRowForRule(group.rules[i], group));
					}
				}

				return table;
			};
			/*
			 * Create the rule data for the filter
			 */
			this.createTableRowForRule = function(rule, group) {
				// save current entity in a variable so that it could
				// be referenced in anonimous method calls

				var that = this, $t = getGrid(), tr = $j("<tr></tr>"),
				// document.createElement("tr"),

				// first column used for padding
				// tdFirstHolderForRule = document.createElement("td"),
				i, op, trpar, cm, str = "", selected;
				// tdFirstHolderForRule.setAttribute("class", "first");
				tr.append("<td class='first'></td>");

				// create field container
				var ruleFieldTd = $j("<td class='columns'></td>");
				tr.append(ruleFieldTd);

				// dropdown for: choosing field
				var ruleFieldSelect = $j("<select></select>"), ina, aoprs = [];
				ruleFieldTd.append(ruleFieldSelect);
				ruleFieldSelect
					.bind(
						'change',
						function() {
							rule.field = $j(ruleFieldSelect).val();

							trpar = $j(this).parents("tr:first");
							for (i = 0; i < that.p.columns.length; i++) {
								if (that.p.columns[i].name === rule.field) {
									cm = that.p.columns[i];
									break;
								}
							}
							if (!cm) {
								return;
							}
							if (!cm.id) cm.id = $j.jrule.randId();
							if (isIE && cm.inputtype === "text") {
								if (!cm.size) {
									cm.size = 10;
								}
							}
							var elm = $j.jrule.createEl.call(that, cm.inputtype, cm, "", true, that.p.ajaxSelectOptions || {}, true);
							$j(elm).addClass("input-elm");

							if (cm.columnOperators) {
								op = cm.columnOperators;
							} else {
								op = that.p.commonOperators;
							}
							// operators
							var s = "";
							for (i = 0; i < op.length; i++) {
								if (i === 0) {
									rule.op = op[i];
								}
								s += "<option value='" + op[i] + "'>" + that.p.i18n[op[i]] + "</option>";
							}
							$j(".selectopts", trpar).empty().append(s);
							$j(".selectopts", trpar)[0].selectedIndex = 0;
							if ($j.jrule.msie && $j.jrule.msiever() < 9) {
								var sw = parseInt($j("select.selectopts", trpar)[0].offsetWidth, 10) + 1;
								$j(".selectopts", trpar).width(sw);
								$j(".selectopts", trpar).css("width", "auto");
							}
							// data
							$j(".data", trpar).empty().append(elm);
							$j.jrule.bindEv.call($t, elm, cm);
							$j(".input-elm", trpar)
								.bind(
									'change',
									function(e) {
										var tmo = $j(this).hasClass("ui-autocomplete-input") ? 200 : 0;
										setTimeout(
											function() {
												var elem = e.target;
												rule.data = elem.nodeName.toUpperCase() === "SPAN" && cm && $j.isFunction(cm.dataResolver) ? cm.dataResolver
													.call($t, $j(elem).children(".customelement:first"), 'get') : elem.value;
												that.onchange(); // signals that the filter has
												// changed
											}, tmo);
									});
							setTimeout(function() { // IE, Opera, Chrome
								rule.data = $j(elm).val();
								that.onchange(); // signals that the filter has changed
							}, 0);
						});

				// populate drop down with user provided column definitions
				var j = 0;
				for (i = 0; i < that.p.columns.length; i++) {
					// but show only serchable and serchhidden = true fields
					var searchable = true, hidden = false, ignoreHiding = false;
					if ((ignoreHiding && searchable) || (searchable && !hidden)) {
						selected = "";
						if (rule.field === that.p.columns[i].name) {
							selected = " selected='selected'";
							j = i;
						}
						str += "<option value='" + that.p.columns[i].name + "'" + selected + ">" + that.p.columns[i].label
							+ "</option>";
					}
				}
				ruleFieldSelect.append(str);

				// create operator container
				var ruleOperatorTd = $j("<td class='operators'></td>");
				tr.append(ruleOperatorTd);
				cm = p.columns[j];
				// create it here so it can be referentiated in the onchange event
				// var RD = that.createElement(rule, rule.data);
				cm.id = $j.jrule.randId();
				if (isIE && cm.inputtype === "text") {
					if (!cm.size) {
						cm.size = 10;
					}
				}
				var ruleDataInput = $j.jrule.createEl.call(that, cm.inputtype, cm, rule.data, true, that.p.ajaxSelectOptions
					|| {}, true);
				if (rule.op === 'nu' || rule.op === 'nn') {
					$j(ruleDataInput).attr('readonly', 'true');
					$j(ruleDataInput).attr('disabled', 'true');
				} // retain the state of disabled text fields in case of null ops
				// dropdown for: choosing operator
				var ruleOperatorSelect = $j("<select class='selectopts'></select>");
				ruleOperatorTd.append(ruleOperatorSelect);
				ruleOperatorSelect.bind('change', function() {
					rule.op = $j(ruleOperatorSelect).val();
					rule.opText = that.p.i18n[$j(ruleOperatorSelect).val()];

					trpar = $j(this).parents("tr:first");
					var rd = $j(".input-elm", trpar)[0];
					if (rule.op === "nu" || rule.op === "nn") { // disable for operator
						// "is null" and "is not
						// null"
						rule.data = "";
						if (rd.tagName.toUpperCase() !== 'SELECT') rd.value = "";
						rd.setAttribute("readonly", "true");
						rd.setAttribute("disabled", "true");
					} else {
						if (rd.tagName.toUpperCase() === 'SELECT') rule.data = rd.value;
						rd.removeAttribute("readonly");
						rd.removeAttribute("disabled");
					}

					that.onchange(); // signals that the filter has changed
				});

				// populate drop down with all available operators
				if (cm.columnOperators) {
					op = cm.columnOperators;
				} else {
					op = that.p.commonOperators;
				}
				str = "";
				for (i = 0; i < op.length; i++) {
					selected = rule.op === op[i] ? " selected='selected'" : "";
					str += "<option value='" + op[i] + "'" + selected + ">" + that.p.i18n[op[i]] + "</option>";
				}
				ruleOperatorSelect.append(str);
				// create data container
				var ruleDataTd = $j("<td class='data'></td>");
				tr.append(ruleDataTd);

				// textbox for: data
				// is created previously
				// ruleDataInput.setAttribute("type", "text");
				ruleDataTd.append(ruleDataInput);
				$j.jrule.bindEv.call($t, ruleDataInput, cm);
				$j(ruleDataInput).addClass("input-elm").bind(
					'change',
					function() {
						rule.data = cm.inputtype === 'custom' ? cm.dataResolver.call($t, $j(this).children(".customelement:first"),
							'get') : $j(this).val();
						that.onchange(); // signals that the filter has changed
					});

				// create action container
				var ruleDeleteTd = $j("<td></td>");
				tr.append(ruleDeleteTd);

				// create button for: delete rule
				if (this.p.ruleButtons === true) {
					var ruleDeleteInput = $j("<input type='button' value='-' title='Delete rule' class='delete-rule ui-del'/>");
					ruleDeleteTd.append(ruleDeleteInput);
					// $j(ruleDeleteInput).html("").height(20).width(30).button({icons:
					// {
					// primary: "ui-icon-minus", text:false}});
					ruleDeleteInput.bind('click', function() {
						// remove rule from group
						for (i = 0; i < group.rules.length; i++) {
							if (group.rules[i] === rule) {
								group.rules.splice(i, 1);
								break;
							}
						}

						that.reDraw(); // the html has changed, force reDraw

						that.onchange(); // signals that the filter has changed
						return false;
					});
				}
				return tr;
			};

			this.getStringForGroup = function(group, isSys) {
				var s = "(", index;
				var opt = isSys ? group.groupOp : group.groupOpText;
				if (group.groups !== undefined) {
					for (index = 0; index < group.groups.length; index++) {
						if (s.length > 1) {
							s += " " + opt + " ";
						}
						s += this.getStringForGroup(group.groups[index], isSys);
					}
				}

				if (group.rules !== undefined) {
					for (index = 0; index < group.rules.length; index++) {
						if (s.length > 1) {
							s += " " + opt + " ";
						}
						s += this.getStringForRule(group.rules[index], isSys);
					}
				}

				s += ")";

				if (s === "()") {
					return ""; // ignore groups that don't have rules
				}
				return s;
			};
			this.getStringForRule = function(rule, isSys) {
				var i, ret, val; // jrule
				var showl = isSys ? rule.field : rule.fieldText;
				var opC = isSys ? rule.op : rule.opText;

				if (typeof rule.data === 'object')
					val = isSys ? rule.data.value : rule.data.display;
				else
					val = rule.data;
				ret = showl + " " + opC + " " + val;
				return ret;
			};

			this.resetFilter = function() {
				this.p.filter = $j.extend(true, {}, this.p.initFilter);
				this.reDraw();
				this.onchange();
			};
			this.toUserFriendlyString = function() {
				return this.getStringForGroup(p.filter, false);
			};
			this.toSysString = function() {
				return this.getStringForGroup(p.filter, true);
			};

			// Here we init the filter
			this.reDraw();

			// mark is as created so that it will not be created twice on this
			// element
			this.filter = true;
		});
};